/*INCLUDES-------------------------------------------------------------------*/

#include <avr/io.h>
#include <util/delay_basic.h>

#include "ses_display.h"
#include "ses_framebuffer.h"
#include "ses_common.h"

/*PRIVATE PROTOTYPES---------------------------------------------------------*/

/**
 * Writes a byte into the RAM of the selected LCD controller
 *
 * @param data	a byte to write
 */
static void writeData(uint8_t data);

/**
 * Writes a command to the selected LCD controller
 * @param	a byte to write
 */
static void writeCommand(uint8_t data);

/**
 * Selects one controller of the LCD
 * @param sel	selected controller (either 0 or 1)
 */
static void selectController(uint8_t sel);

/**
 * Reads status byte from  selected LCD controller
 */
static uint8_t readStatus();

/**
 * Waits until the selected controller is non-busy.
 */
static void waitBusy();

/*FUNCTION DEFINITION--------------------------------------------------------*/

void display_init() {

	//init control port for A0, CS, WR, RD
	DDR(LCD_CONTROL_PORT) |= (1 << DISPLAY_CS_PIN) | (1 << DISPLAY_A0_PIN) | (1
			<< DISPLAY_WR_PIN) | (1 << DISPLAY_RD_PIN);
	//WR and RD are high when doing nothing
	DISPLAY_WR_PORT |= (1 << DISPLAY_WR_PIN);
	DISPLAY_RD_PORT |= (1 << DISPLAY_RD_PIN);

	//reset, set column start address and turn display on, both controller
	for (int i = 0; i < CONTROLLER_CNT; i++) {
		selectController(i);
		writeCommand(DISPLAY_COMMAND_RESET);
		writeCommand(DISPLAY_COMMAND_SET_DISPLAY_START_ADDR_TO_ZERO);
		writeCommand(DISPLAY_COMMAND_ON);
	}
}

void display_write(uint8_t page, uint8_t column, uint8_t data) {
	//check if its an existing page and column number
	if ((page >= DISPLAY_PAGES) || (column >= DISPLAY_COLUMNS)) {
		return;
	}

	//check if controller 1 or 2 should write the column
	if (column < (DISPLAY_COLUMNS / 2)) {
		selectController(0);
	} else {
		selectController(1);
		//column counter has also to start at 0 for the second controller
		column -= DISPLAY_COLUMNS / 2;
	}

	//set page and column address, save data in ram
	writeCommand(DISPLAY_COMMAND_PAGE | page);
	writeCommand(DISPLAY_COMMAND_COLUMN | column);
	writeData(data);

}

void display_write_page(uint8_t page, uint8_t *data) {

	selectController(0);
	writeCommand(DISPLAY_COMMAND_PAGE | page);
	//write page of controller one
	writeCommand(DISPLAY_COMMAND_COLUMN);
	//column counter is incremented by the LCD controller
	for (int i = 0; i < (DISPLAY_COLUMNS / 2); i++) {
		writeData(data[i]);
	}
	//continue page with controller two
	selectController(1);
	writeCommand(DISPLAY_COMMAND_PAGE | page);
	writeCommand(DISPLAY_COMMAND_COLUMN);
	for (int i = 61; i < DISPLAY_COLUMNS; i++) {
		writeData(data[i]);
	}
}

void display_clear() {

	/* We use Framebuffer, so every clear will be done in the framebuffer first */
	/*for(int page=0; page<DISPLAY_PAGES; page++){
	 for(int column=0; column<DISPLAY_COLUMNS; column++){
	 display_write(page, column, 0);
	 }
	 }*/

	/* Clear the framebuffer */
	ses_framebuffer_init();

	/* Write the framebuffer to the display */
	ses_framebuffer_flush();
}

void writeData(uint8_t data) {
	waitBusy();

	//prepare writing of data
	DDR(DISPLAY_DATA_PORT) = 0xFF;
	DISPLAY_A0_PORT |= (1 << DISPLAY_A0_PIN);
	DISPLAY_RD_PORT |= (1 << DISPLAY_RD_PIN);

	//start writing
	DISPLAY_WR_PORT &= ~(1 << DISPLAY_WR_PIN);

	DISPLAY_DATA_PORT = data;
	_delay_loop_1(1);

	//stop writing
	DISPLAY_WR_PORT |= (1 << DISPLAY_WR_PIN);
}

void writeCommand(uint8_t data) {
	waitBusy();

	//prepare sending of command
	DDR(DISPLAY_DATA_PORT) = 0xFF;
	DISPLAY_A0_PORT &= ~(1 << DISPLAY_A0_PIN);
	DISPLAY_RD_PORT |= (1 << DISPLAY_RD_PIN);

	//start sending
	DISPLAY_WR_PORT &= ~(1 << DISPLAY_WR_PIN);

	DISPLAY_DATA_PORT = data;
	_delay_loop_1(1);

	//stop sending
	DISPLAY_WR_PORT |= (1 << DISPLAY_WR_PIN);
}

uint8_t readStatus() {
	//prepare readout of display status bits
	DDR(DISPLAY_DATA_PORT) = 0x00;
	DISPLAY_A0_PORT &= ~(1 << DISPLAY_A0_PIN);
	DISPLAY_WR_PORT |= (1 << DISPLAY_WR_PIN);

	//start reading
	DISPLAY_RD_PORT &= ~(1 << DISPLAY_RD_PIN);
	_delay_loop_1(1);

	uint8_t status;
	status = PIN(DISPLAY_DATA_PORT);

	//stop reading
	DISPLAY_RD_PORT |= (1 << DISPLAY_RD_PIN);

	return status;
}

void selectController(uint8_t sel) {
	if (sel == 0) {
		DISPLAY_CS_PORT &= ~(1 << DISPLAY_CS_PIN);
	} else {
		DISPLAY_CS_PORT |= (1 << DISPLAY_CS_PIN);
	}
}

void waitBusy() {

	uint8_t status = readStatus();
	while (status & DISPLAY_STATUS_BUSY) {
		status = readStatus();
	}

}

